package com.gcloud.mesh.dcs.aop;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.servlet.http.HttpServletRequest;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import com.gcloud.mesh.dcs.annotation.ParamSecurity;
import com.gcloud.mesh.header.exception.BaseException;
import com.gcloud.mesh.header.msg.BaseMsg;

import lombok.extern.slf4j.Slf4j;

@Configuration
@Aspect
@Slf4j
public class ParamSecurityAop {

	public static Set<String> extraSet = new HashSet<>();

	static {

		extraSet.add("_t");
		extraSet.add("id");
		extraSet.add("datacenterId");
		extraSet.add("nodeId");
		extraSet.add("resourceId");
		extraSet.add("meter");
		extraSet.add("order");

		for (int i = 0; i < 30; i++) {
			extraSet.add("factors[" + i + "].id");
			extraSet.add("factors[" + i + "].diff");
			extraSet.add("factors[" + i + "].scoreSample");
			extraSet.add("factors[" + i + "].scoreType");
			extraSet.add("factors[" + i + "].weight");
			extraSet.add("factors[" + i + "].enabled");
			extraSet.add("factors[" + i + "].factorName");
		}

		for (int i = 0; i < 10; i++) {
			extraSet.add("tool[" + i + "]");
		}

		for (int i = 0; i < 10; i++) {
			extraSet.add("datacenterIds[" + i + "]");
		}

		for (int i = 0; i < 10; i++) {
			extraSet.add("items[" + i + "].type");
			extraSet.add("items[" + i + "].enabled");
		}

		// 亚健康阈值设置
		for (int i = 0; i < 4; i++) {
			extraSet.add("subhealthThresholds[" + i + "].id");
			extraSet.add("subhealthThresholds[" + i + "].level");
			extraSet.add("subhealthThresholds[" + i + "].meter");
			extraSet.add("subhealthThresholds[" + i + "].type");
			extraSet.add("subhealthThresholds[" + i + "].lowerThreshold");
			extraSet.add("subhealthThresholds[" + i + "].upperThreshold");
		}

		// 告警策略
		for (int i = 0; i < 20; i++) {
			extraSet.add("ruleVos[" + i + "].id");
			extraSet.add("ruleVos[" + i + "].comparisonOperator");
			extraSet.add("ruleVos[" + i + "].duration");
			extraSet.add("ruleVos[" + i + "].level");
			extraSet.add("ruleVos[" + i + "].threshold");
			extraSet.add("ruleVos[" + i + "].statistics");
			extraSet.add("ruleVos[" + i + "].enabled");
			extraSet.add("ruleVos[" + i + "].meter");
			extraSet.add("ruleVos[" + i + "].resourceType");
			extraSet.add("ruleVos[" + i + "].meterChnName");
			extraSet.add("ruleVos[" + i + "].policyId");
		}

		for (int i = 0; i < 30; i++) {
			extraSet.add("resourceTypes[" + i + "]");
		}
		// 增加instances
		for (int i = 0; i < 10; i++) {
			extraSet.add("instances[" + i + "].instanceId");
			extraSet.add("instances[" + i + "].instanceName");
		}
	}

	@Pointcut("execution(* com.gcloud.mesh.*.*.*Controller.*(..))")
	public void excuteService() {

	}

	// 执行方法前的拦截方法
	@Before("excuteService()")
	public void doBeforeMethod(JoinPoint joinPoint) {

		MethodSignature msig = (MethodSignature) joinPoint.getSignature();
		ParamSecurity annotation = msig.getMethod().getAnnotation(ParamSecurity.class);

		if (annotation != null && annotation.enabled()) {
			Object[] obj = joinPoint.getArgs();
			List<String> names = new ArrayList<String>();
			for (Object argItem : obj) {
				if (argItem instanceof BaseMsg) {
					names.addAll(getFieldNames(argItem));
				}
				if (argItem instanceof String) {
					String[] params = msig.getParameterNames();
					names.addAll(Arrays.asList(params));
				}
			}
			ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder
					.getRequestAttributes();
			HttpServletRequest request = attributes.getRequest();
			Enumeration<String> paramter = request.getParameterNames();
			List<String> inputParams = new ArrayList<String>();
			while (paramter.hasMoreElements()) {
				String str = (String) paramter.nextElement();
				inputParams.add(str);
			}
			names = extraParam(names);
			names.add("title");
			names.add("content");
			boolean checkRes = checkParam(inputParams, names);
			log.info("[ParamSecurityAop][doBeforeMethod] 入参安全校验结果：{}", checkRes);
			if (!checkRes) {
				String content = "";
				if (inputParams.size() > 0) {
					content = String.join(",", inputParams);
				}
				throw new BaseException("000008", "存在非法参数: " + content);
			}
		}
	}

	/**
	 * 后置最终通知（目标方法只要执行完了就会执行后置通知方法）
	 */
	@After("excuteService()")
	public void doAfterAdvice(JoinPoint joinPoint) {

	}

	/**
	 * 获取一个类的所有字段名（包括父类）
	 * 
	 * @param obj
	 * @return
	 */
	private List<String> getFieldNames(Object obj) {
		if (obj == null) {
			return new ArrayList<String>();
		}
		List<String> names = new ArrayList<String>();
		List<Field> fieldList = new ArrayList<>();
		Class clazz = obj.getClass();
		try {
			while (clazz != null) {// 当父类为null的时候说明到达了最上层的父类(Object类).
				fieldList.addAll(Arrays.asList(clazz.getDeclaredFields()));
				clazz = clazz.getSuperclass(); // 得到父类,然后赋给自己
			}
		} catch (Exception e) {
			log.info("[ParamSecurityAop][getFieldNames] 字段名获取异常!");
		}
		for (Field f : fieldList) {
			names.add(f.getName());
		}
		Set<String> set = new HashSet<>(names);
		List<String> newList = new ArrayList<>(set);
		return newList;
	}

	/**
	 * 检查是否有非法入参
	 * 
	 * @param inputParams
	 * @param allowParams
	 * @return
	 */
	private boolean checkParam(List<String> inputParams, List<String> allowParams) {
		inputParams.removeAll(allowParams);
		if (inputParams.size() == 0) {
			return true;
		}
		return false;
	}

	private List<String> extraParam(List<String> inputParams) {
		Set<String> paramSet = new HashSet<>(inputParams);
		paramSet.addAll(extraSet);
		List<String> newList = new ArrayList<>(paramSet);
		return newList;
	}
}
